package core.domain.query.paging;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import core.domain.IdEntity;
import core.domain.Pagable;

/**
 * <p>
 * The class <code>AbstractPagingCursor</code> is a skeleton implementation of {@link Pageable} intended to be used to
 * generate Paging query. It maintains info such as page size, page count and total record size, Subclasses should
 * implements {@link #content()} and {@link #totalSize()}.
 * </p>
 * 
 * @version 1.1
 * @history
 * @since TLQuery 1.0
 * @see Pagable
 */
@SuppressWarnings("all")
public abstract class AbstractPagingCursor<T extends IdEntity> implements Pageable<T> {

    protected int pageSize = -1;

    protected int lastPage = -1;

    protected int currentPage = -1;

    protected int totalSize = -1;

    protected List<T> buffer;

    protected Log logger;

    public AbstractPagingCursor() {
        super();
        logger = LogFactory.getLog(getClass());
        currentPage = 0;
    }

    public AbstractPagingCursor(int pageSize) {
        super();
        if (pageSize <= 0) {
            throw new IllegalArgumentException("Page Size cannot be less than or equal to 0.");
        }
        this.pageSize = pageSize;
        currentPage = 0;
    }

    public int currentPage() {
        return currentPage;
    }

    public void next() {
        int maxNum = pageCount();
        if (currentPage == maxNum - 1) {
            throw new IndexOutOfBoundsException("Try to browse to page " + (currentPage + 1)
                    + " while max page num is " + maxNum);
        }
        currentPage++;
    }

    public void prev() {
        if (currentPage == 0) {
            throw new IndexOutOfBoundsException("Try to browse to negative page number.");
        }
        currentPage--;
    }

    public void to(int pageNum) throws IndexOutOfBoundsException {
        int maxNum = pageCount();
        if (pageNum >= maxNum) {
            throw new IndexOutOfBoundsException("Try to browse to page " + pageNum + " while max page num is "
                    + maxNum);
        }
        currentPage = pageNum;
    }

    public int pageCount() {
        return (int) Math.ceil(((double) totalSize()) / ((double) pageSize()));
    }

    public int pageSize() {
        if (pageSize <= 0) {
            pageSize = totalSize();
        }
        return pageSize;
    }

    void checkRuntime() {
    }

    public List<T> content() {
        if (currentPage == pageCount() && currentPage == 0) {
            return new ArrayList<T>();
        }
        if (currentPage >= pageCount() || currentPage < 0) {
            throw new IndexOutOfBoundsException("Current Page : " + currentPage);
        }
        if (lastPage == currentPage) {
            return buffer;
        }
        try {
            checkRuntime();
            lastPage = currentPage;
            buffer = fetchContent();
            return buffer;
        } catch (Exception e) {
            logger.warn("Exception occurs in AbstractPagingCursor", e);
            return new ArrayList<T>();
        }
    }

    protected abstract List<T> fetchContent();

    protected abstract int calcTotalSize();

    /**
     * Set alwaysRefreshSize to false to cache the total size result
     */
    private boolean alwaysRefreshSize = false;

    public boolean isAlwaysRefreshSize() {
        return alwaysRefreshSize;
    }

    public void setAlwaysRefreshSize(boolean alwaysRefreshSize) {
        this.alwaysRefreshSize = alwaysRefreshSize;
    }

    public int totalSize() {
        if (alwaysRefreshSize || totalSize == -1) {
            totalSize = calcTotalSize();
        }
        return totalSize;
    }

    public int currentPos() {
        return pageSize() * pageCount();
    }
}
